/*
  flamingo - 2D Game Engine
  Copyright (C) 2009 Bradley Zeis

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  Bradley Zeis
  flamingoengine@gmail.com
*/

#define FLMATH      1
#define SCREEN      1
#define IMAGE       1
#define MESH        1
#define FLCOLOR     1
#define SPRITE      1

#include "flamingo.h"
#include "flmath.h"
#include "sprite.h"
#include "screen.h"
#include <math.h>
#include <gl/gl.h>

extern PyTypeObject __declspec(dllimport) Color_Type;
extern PyTypeObject __declspec(dllimport) Vector_Type;
extern PyTypeObject __declspec(dllimport) Polygon_Type;
extern PyTypeObject __declspec(dllimport) Rect_Type;

extern PyTypeObject __declspec(dllimport) Bone_Type;

PyTypeObject __declspec(dllexport) Painter_Type;
PyTypeObject __declspec(dllexport) Screen_Type;

PyObject *core_module;
PyObject *SCREENS;
PyObject *MASTER_GROUP;

/* -------- Painter METHODS -------- */
/* Initialization */
void Painter_dealloc(Painter *self) {
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Painter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Painter *self;
    self = (Painter *)type->tp_alloc(type, 0);
    return (PyObject *)self;
}

int Painter_init(Painter *self, PyObject *args) {
    return 0;
}

/* Presentation */
PyObject *Painter_repr(Painter *self) {
    char string[64];
    sprintf(string, "<screen.Painter(%i)>", (int)self);
    return PyString_FromString(string);
}

PyObject *Painter_str(Painter *self) {
    return Painter_repr(self);
}

/* General Functions */
PyObject *Painter_draw(Painter *self, PyObject *args) {
    /* Make sure MASTER_GROUP is not None */
    if (MASTER_GROUP == Py_None) {
        Py_DECREF(MASTER_GROUP);
        PyObject *cdict = PyModule_GetDict(core_module);
        PyObject *data = PyDict_GetItemString(cdict, "_Data");
        MASTER_GROUP = PyObject_GetAttrString(data, "master_spr_group");
    }

    /* Draw Screens */
    glClear(GL_COLOR_BUFFER_BIT);

    int i, l = PySequence_Length(SCREENS);
    Screen *current;
    PyObject *result;

    for (i = 0; i < l; i++) {
        current = (Screen *)PyList_GetItem(SCREENS, i);

        glViewport(current->local_rect->center->pt->x - (current->local_rect->width / 2),
                   current->local_rect->center->pt->y - (current->local_rect->height / 2),
                   current->local_rect->width, current->local_rect->height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, current->local_rect->width, 0, current->local_rect->height, -1, 1);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        result = Screen_draw(current, args);
        Py_XDECREF(result);
    }
    
    
    /* "Radar" */
    current = (Screen *)PyList_GetItem(SCREENS, 0);
    
    glViewport(current->width - (current->width / 6) - 3, 4, current->width / 6, current->height / 6);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, current->local_rect->width, 0, current->local_rect->height, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glBegin(GL_QUADS);
    
    glColor4f(1, 1, 1, .75);
    glVertex2f(0, 0);
    glVertex2f(current->width, 0);
    glVertex2f(current->width, current->height);
    glVertex2f(0, current->height);
    
    glEnd();
    
    glTranslatef(current->center->pt->x, current->center->pt->y, 0);
    glRotatef(current->orientation, 0, 0, 1);
    glScalef(.125 / current->zoom, .125 / current->zoom, 1);
    
    glBegin(GL_QUADS);
    
    glColor4f(.75, 0, 0, .75);
    glVertex2f(-current->width / 2, -current->height / 2);
    glVertex2f(-current->width / 2, current->height / 2);
    glVertex2f(current->width / 2, current->height / 2);
    glVertex2f(current->width / 2, -current->height / 2);
    
    glEnd();
    // End test
    
    
    Py_RETURN_NONE;
}

/* Method Declaration */
PyMethodDef Painter_methods[] = {
    {"draw", (PyCFunction)Painter_draw, METH_VARARGS, NULL},
    {NULL}
};

/* -------- SCREEN METHODS -------- */
/* Initialization */
void Screen_dealloc(Screen *self) {
    Py_XDECREF(self->center);
    int i;
    for (i = 0; i < self->length; i++) {
        Py_XDECREF(self->points[i]);
        Py_XDECREF(self->edges[i]);
    }
    PyMem_Del(self->points);
    PyMem_Del(self->edges);
    PyMem_Del(self->orients);
    Py_XDECREF(self->local_rect);
    Py_XDECREF(self->bound_rect);
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Screen_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Screen *self;
    self = (Screen *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->center = uNewVector(0, 0);
        if (self->center == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->points = NULL;
        self->edges = NULL;
        self->orients = NULL;

        self->length = 4;
        self->width = 0;
        self->height = 0;
        self->orientation = 0;
        self->zoom = 1.0;
        self->flags = 0;
    }

    return (PyObject *)self;
}

int Screen_init(Screen *self, PyObject *args) {
    PyObject *bottomleft;
    double width, height;
    unsigned char flags=0;
    if (!PyArg_ParseTuple(args, "Odd|b", &bottomleft, &width, &height, &flags))
        return -1;

    /* Get bottomleft coordinates */
    PyObject *xi, *yi;
    double x, y;

    xi = GETITEM(bottomleft, 0);
    yi = GETITEM(bottomleft, 1);
    if (xi == NULL || yi == NULL) {
        PyErr_Format(PyExc_TypeError, "argument must be Vector-like");
        return -1;
    }
    if (!(PyNumber_Check(xi) || PyNumber_Check(yi))) {
        PyErr_Format(PyExc_TypeError, "argument must be Vector-like");
        return -1;
    }
    x = PyFloat_AsDouble(xi);
    y = PyFloat_AsDouble(yi);
    Py_DECREF(xi);
    Py_DECREF(yi);

    /* Initialize self */
    x += width / 2.0;
    y += height / 2.0;

    PyObject *result;
    result = PyObject_CallMethod((PyObject *)&Rect_Type, "__init__", "O(dd)dd", self, x, y, width, height);
    if (result == NULL) {
        return -1;
    }
    Py_DECREF(result);

    /* Assign Attributes */
    self->local_rect = uNewRect(x, y, width, height);
    if (self->local_rect == NULL) {
        return -1;
    }
    result = PyObject_CallMethod((PyObject *)self->local_rect, "enable", "b", POLYGON_AXIS_ALIGNED);
    Py_DECREF(result);

    self->bound_rect = uNewRect(x, y, width, height);
    if (self->local_rect == NULL) {
        return -1;
    }

    self->flags = flags;
    return 0;
}

/* Presentation */
PyObject *Screen_repr(Screen *self) {
    char string[64];
    sprintf(string, "<screen.Screen(%i)>", (int)self);
    return PyString_FromString(string);
}

PyObject *Screen_str(Screen *self) {
    return Screen_repr(self);
}

/* General Functions */
void Screen_load_identity(Screen *self) {
    glLoadIdentity();
    glTranslatef(self->width/2.0, self->height/2.0, 0);
    glRotatef(self->orientation, 0, 0, 1);
}

PyObject *Screen_draw(Screen *self, PyObject *args) {
    double interpolation;
    if (!PyArg_ParseTuple(args, "d", &interpolation))
        return NULL;

    /* Get Sprite list */
    PyObject *sprites;
    if (self->flags & SCREEN_CULL_SPRITES) {
        sprites = PyObject_CallMethod(MASTER_GROUP, "active_sprites", NULL);
    } else {
        sprites = PyObject_CallMethod(MASTER_GROUP, "sprites", NULL);
    }

    /* Determine bound_rect */
    PyObject *val = Py_BuildValue("dd", (self->local_rect->width * 1.3) / self->zoom,
                                  (self->local_rect->height * 1.3) / self->zoom);
    Rect_setsize(self->bound_rect, val, CLOSURE);
    Py_DECREF(val);
    self->bound_rect->center->pt->x = self->center->pt->x;
    self->bound_rect->center->pt->y = self->center->pt->y;
    val = PyFloat_FromDouble(self->orientation);
    Polygon_setorientation((Polygon *)self->bound_rect, val, CLOSURE);
    Py_DECREF(val);

    /* Draw Sprites */
    glEnable(GL_TEXTURE_2D);

    Sprite *spr;
    PyObject *result;
    double x, y;

    int i, l = PySequence_Length(sprites);
    for (i = 0; i < l; i++) {
        Screen_load_identity(self);

        spr = (Sprite *)PyList_GetItem(sprites, i);
        Sprite_interpolate_internal(spr, interpolation);
        
        /* Check if Sprite in bounds */
        val = Py_BuildValue("(O)", spr->interstate->bound);
        result = Polygon_intersects((Polygon *)self->bound_rect, val);
        Py_DECREF(val);
        
        if (result == Py_False) {
            Py_DECREF(result);
            continue;
        }
        Py_DECREF(result);

        /* Position Sprite */
        x = (spr->interstate->bound->center->pt->x - self->center->pt->x) * self->zoom;
        y = (spr->interstate->bound->center->pt->y - self->center->pt->y) * self->zoom;
        glTranslatef(x, y, 0);
        glRotatef(spr->interstate->bound->orientation, 0, 0, 1);
        glScalef(self->zoom, self->zoom, 1); 

        /* Draw Sprite */
        //glBindTexture(GL_TEXTURE_2D, spr->texture->textures[spr->texture->frame]);
        glColor4f(spr->interstate->color->r, spr->interstate->color->g,
                  spr->interstate->color->b, spr->interstate->color->a);

        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glEnableClientState(GL_VERTEX_ARRAY);
        
        // Interpolates everything but the final transformations of the meshes. Great.
        glTexCoordPointer(2, GL_DOUBLE, 0, spr->mesh->tcoords);
        glVertexPointer(2, GL_DOUBLE, 0, spr->mesh->vcoords);
        
        glDrawArrays(GL_LINE_LOOP, 0, spr->state->bound->length);
        
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        glDisableClientState(GL_VERTEX_ARRAY);
        
        
        // Draw bones (test test test)
        if (spr->bones != NULL) {
            glColor4f(0, 0.75, 0, 0.75 * spr->interstate->color->a);
            udraw_bones(spr->bones);
        }
    }

    glDisable(GL_TEXTURE_2D);
    Py_RETURN_NONE;
}

void udraw_bones(Bone *bone) {
    
    glBegin(GL_LINES);
    glVertex2f(0, 0);
    glVertex2f(bone->v->pt->x, bone->v->pt->y);
    glEnd();
    glPushMatrix();
    glTranslatef(bone->v->pt->x, bone->v->pt->y, 0);
    glRotatef(RADTODEG(atan2(bone->v->pt->y, bone->v->pt->x)), 0, 0, 1);
    
    int i;
    for (i = 0; i < bone->child_count; i++) {
        udraw_bones(bone->children[i]);
    }
    
    //glTranslatef(-bone->v->pt->x, -bone->v->pt->y, 0);
    glPopMatrix();
}

/* Method Declaration */
PyMemberDef Screen_members[] = {
    {"zoom", T_DOUBLE, offsetof(Screen, zoom), 0, NULL},
    {"flags", T_UBYTE, offsetof(Screen, flags), 0, NULL},
    {"local_rect", T_OBJECT, offsetof(Screen, local_rect), 0, NULL},
    {NULL}
};

PyMethodDef Screen_methods[] = {
    {"draw", (PyCFunction)Screen_draw, METH_VARARGS, NULL},
    {NULL}
};

/* -------- TYPE DECLARATIONS -------- */
PyTypeObject Painter_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.screen.Painter",                  /* tp_name */
    sizeof(Painter),                            /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Painter_dealloc,                /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Painter_repr,                     /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Painter_str,                      /* tp_str */
    0,                                          /*i tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Painter_methods,                            /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Painter_init,                     /* tp_init */
    0,                                          /* tp_alloc */
    Painter_new,                                /* tp_new */
};

PyTypeObject Screen_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.screen.Screen",                   /* tp_name */
    sizeof(Screen),                             /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Screen_dealloc,                 /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Screen_repr,                      /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Screen_str,                       /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Screen_methods,                             /* tp_methods */
    Screen_members,                             /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Screen_init,                      /* tp_init */
    0,                                          /* tp_alloc */
    Screen_new,                                 /* tp_new */
};

/* -------- MODULE INIT -------- */
PyMODINIT_FUNC
initscreen(void)
{
    PyObject *m;

    /* Imports */
    core_module = ImportModule("flamingo.core");
    if (core_module == NULL)
        Py_Exit(0);

    PyObject *cdict = PyModule_GetDict(core_module);
    PyObject *data = PyDict_GetItemString(cdict, "_Data");
    SCREENS = PyObject_GetAttrString(data, "screens");
    MASTER_GROUP = PyObject_GetAttrString(data, "master_spr_group");

    /* Initialize Types */
    Screen_Type.tp_base = &Rect_Type;

    if (PyType_Ready(&Painter_Type) < 0)
        return;
    if (PyType_Ready(&Screen_Type) < 0)
        return;

    /* Initialize Module */
    m = Py_InitModule("screen", NULL);
    if (m == NULL)
        return;

    Py_INCREF(&Painter_Type);
    PyModule_AddObject(m, "Painter", (PyObject *)&Painter_Type);
    Py_INCREF(&Screen_Type);
    PyModule_AddObject(m, "Screen", (PyObject *)&Screen_Type);

    Py_DECREF(core_module);
    Py_DECREF(SCREENS);
    Py_DECREF(MASTER_GROUP);
}
